package sonic.game.tile;

import sonic.game.Game;
import sonic.game.util.Vector2D;

/**
 * 
 * Dynamic Tile
 * 
 * Base class for all moving objects including items and game characters.
 * Collision detection is handled here, as are other physics elements like friction, gravity etc.
 * 
 * @author Richard Greenlees
 *
 */
public class DynamicTile extends Tile {

	// Terminal Velocity for gravity
	public float maxFallSpeed = 380.0f;
	
	// The current tiles being collided with in all 4 cardinal directions
	public Tile[] currentCollisions = new Tile[4];
	
	// Which entry in currentCollisions refers to which direction
	protected final int UPPERCOLLISION = 0;
	protected final int LOWERCOLLISION = 1;
	protected final int LEFTCOLLISION = 2;
	protected final int RIGHTCOLLISION = 3;
	
	// Collision box size
	protected float collisionBoxX = 1.0f;
	protected float collisionBoxY = 1.0f;
	
	// How much friction is applied which moving on a solid surface
	protected float defaultFriction = 1.5f;
	protected float friction = defaultFriction;
	
	/** Current direction and speed of travel */
	public Vector2D velocity = new Vector2D();
	
	public enum ePhysicsType {PHYS_FALLING, PHYS_WALKING, PHYS_FLYING, PHYS_NONE};
	public ePhysicsType physicsType = ePhysicsType.PHYS_NONE;
	
	public DynamicTile(Vector2D startPos)
	{
		super(startPos);
	}
	
	public DynamicTile()
	{
		
	}
	
	/** Handle collision detection and physics processes such as gravity and friction */
	public void update(double deltaTime)
	{	
		Vector2D newLoc = new Vector2D();
		super.update(deltaTime);
		// This will check for collisions in the 4 cardinal directions
		if (!bDestroyMe)
		{
			updateCollisions();
		}
		
		if (isWalking())
		{
			// If the player is falling but has now landed on something, put them in the walking state
			if (physicsType == ePhysicsType.PHYS_FALLING)
			{
				physicsType = ePhysicsType.PHYS_WALKING;
			}
			
			// If the tile is travelling too slowly then bring them to a complete stop, otherwise apply friction
			if (Math.abs(velocity.x) > 1.0f)
			{
				// Push the tile in the opposite direction to travel, slowing them down if it stops accelerating 
				velocity.x += (140*friction*deltaTime) * ((velocity.x > 0) ? -1.0f : 1.0f);
			}
			else
			{
				velocity.x = 0;
			}
		}
		else
		{
			// If the tile was travelling on the floor but now no longer is, then put them in the falling state
			if (physicsType != ePhysicsType.PHYS_FLYING && physicsType != ePhysicsType.PHYS_NONE)
			{
				physicsType = ePhysicsType.PHYS_FALLING;
			}
		}
		
		// Apply gravity to falling objects, accelerate downwards until it reaches terminal velocity
		if (physicsType == ePhysicsType.PHYS_FALLING && velocity.y < maxFallSpeed)
		{
			velocity.y += maxFallSpeed*2.0*deltaTime;
		}
		
		// Update the tile's position based on current velocity
		newLoc.x = (float) (location.x + velocity.x*deltaTime);	
		newLoc.y = (float) (location.y + velocity.y*deltaTime);
		setLocation(newLoc);
	}
	
	/** Perform collision detection. Will populate currentCollisions[] with currently colliding tiles and trigger collision events */
	public void updateCollisions()
	{
		boolean upperCollision = false;
		boolean lowerCollision = false;
		boolean leftCollision = false;
		boolean rightCollision = false;
		currentCollisions[0] = null;
		currentCollisions[1] = null;
		currentCollisions[2] = null;
		currentCollisions[3] = null;
		
		if (collisionType == eCollisionType.CT_NONE)
		{
			return;
		}
		
		// Loop through all tiles in the map
		for (Tile a : Game.currentMap.relevantTiles)
		{
			// Don't allow collision with myself, tiles that have no collision or are flagged for deletion
			if (a != this && a.collisionType != eCollisionType.CT_NONE && !a.bDestroyMe)
			{
				// If we haven't already detected a collision above us
				if (!upperCollision)
				{
					// Check to see if any part of us is inside the tile, if so then we're colliding
					upperCollision = (((tileCorners[0].y-2.0f < a.tileCorners[2].y) && (tileCorners[0].y-2.0f > a.tileCorners[0].y)&& ((tileCorners[3].x < a.tileCorners[2].x) && (tileCorners[2].x > a.tileCorners[0].x))));
					if (upperCollision)
					{
						// If the tile blocks all then call the landed on by event, otherwise call touch
						if (a.collisionType == eCollisionType.CT_BLOCKALL)
						{
							landedOnBy(a);
							currentCollisions[UPPERCOLLISION] = a;
						}
						else if (a.collisionType == eCollisionType.CT_TOUCHALL)
						{
							touch(a);
							a.touch(this);
						}
					}
				}
				
				// Repeat for lower collision
				if (!lowerCollision)
				{
					lowerCollision = (((tileCorners[2].y+2.0f > a.tileCorners[0].y) && (tileCorners[2].y+2.0f < a.tileCorners[2].y) && ((tileCorners[3].x < a.tileCorners[2].x) && (tileCorners[2].x > a.tileCorners[0].x))));
					if (lowerCollision)
					{
						if (a.collisionType == eCollisionType.CT_BLOCKALL)
						{
							landedOn(a);
							currentCollisions[LOWERCOLLISION] = a;
						}
						else if (a.collisionType == eCollisionType.CT_TOUCHALL)
						{
							touch(a);
							a.touch(this);
						}
					}
				}
				
				// Repeat for left collisions
				if (!leftCollision)
				{

					leftCollision = (((tileCorners[0].x-2.0f < a.tileCorners[1].x) && (tileCorners[0].x-2.0f > a.tileCorners[0].x) && ((tileCorners[3].y > a.tileCorners[1].y+2.0f) && (tileCorners[0].y < a.tileCorners[2].y-2.0f))));

					if (leftCollision)
					{
						if (a.collisionType == eCollisionType.CT_BLOCKALL)
						{
							if (velocity.x < 0)
							{
								hitWall(a);
							}
							currentCollisions[LEFTCOLLISION] = a;
						}
						else if (a.collisionType == eCollisionType.CT_TOUCHALL)
						{
							touch(a);
							a.touch(this);
						}
					}
				}
				
				// Repeat for right collisions
				if (!rightCollision)
				{
					rightCollision = (((tileCorners[1].x+2.0f > a.tileCorners[0].x) && (tileCorners[1].x+2.0f < a.tileCorners[1].x) && ((tileCorners[3].y > a.tileCorners[1].y+2.0f) && (tileCorners[0].y < a.tileCorners[2].y-2.0f))));
					if (rightCollision)
					{
						if (a.collisionType == eCollisionType.CT_BLOCKALL)
						{
							if (velocity.x > 0)
							{
								hitWall(a);
							}
							currentCollisions[RIGHTCOLLISION] = a;
						}
						else if (a.collisionType == eCollisionType.CT_TOUCHALL)
						{
							touch(a);
							a.touch(this);
						}
					}
				}
				
			}
		}		
	}
	
	/** Unlike the base Tile class, Dynamic Tiles use a collision box rather than their animation for bounds */
	public void updateBounds()
	{
		tileCorners[0] = new Vector2D(location.x, location.y);
		tileCorners[1] = new Vector2D(location.x + collisionBoxX, location.y);
		tileCorners[2] = new Vector2D(location.x + collisionBoxX, location.y + collisionBoxY);
		tileCorners[3] = new Vector2D(location.x, location.y + collisionBoxY);
	}
	
	/** Returns true if the tile is resting on a solid object and has appropriate physics type */
	public boolean isWalking()
	{
		if (physicsType == ePhysicsType.PHYS_FLYING || physicsType == ePhysicsType.PHYS_NONE)
		{
			return false;
		}
		
		return (currentCollisions[LOWERCOLLISION] != null && currentCollisions[LOWERCOLLISION].collisionType == eCollisionType.CT_BLOCKALL);		
	}
	
	/** If struck a solid object while travelling horizontally, stop movement */
	public void hitWall(Tile a)
	{
		velocity.x = 0;
	}
	
	/** If the tile landed on a solid object, make sure it is resting properly on the surface and stop any downwards acceleration */
	public void landedOn(Tile landedOn)
	{
		Vector2D newLoc = new Vector2D();
		newLoc.x = location.x;
		newLoc.y = landedOn.location.y-collisionBoxY;
		velocity.y = 0;
		setLocation(newLoc);
	}
	
	/** If struck from above by a solid object (or accelerated upwards into something) then stop upwards movement */
	public void landedOnBy(Tile a)
	{
		if (velocity.y < 0)
		{
			velocity.y = 0;
		}
	}
	
	/** Return the centre point of the tile */
	public Vector2D getCentre()
	{
		Vector2D result = new Vector2D();
		result.x = tileCorners[0].x + (collisionBoxX/2);
		result.y = tileCorners[0].y + (collisionBoxY/2);
		return result;
	}
	
}
